#include "ipc.h"
//#define wxUSE_DDE_FOR_IPC 0
#include <wx/wx.h>
#include <wx/ipc.h>
#include <wx/base64.h>

#include <map>
#include <vector>
using namespace std;
namespace IPCSERVER {
bool (*ipcOnExecute)(const char *topic, const char *data, int size);
bool (*ipcOnPoke)(const char *topic, const char *item, char *data, int size);
const char*(*ipcOnRequest)(const char *topic, const char *item, int *size);
bool (*ipcOnStartAdvise)(const char *topic, const char *item);
bool (*ipcOnStopAdvise)(const char *topic, const char *item);
void (*ipcOnDisconnected)(const char *topic);
class MyConnection : public wxConnection
{
public:
        MyConnection(const wxString &);
        ~MyConnection();

        virtual bool OnExecute(const wxString& topic, wxChar *data, int size, wxIPCFormat format);
        virtual const void *OnRequest(const wxString& topic, const wxString& item, int *size, wxIPCFormat format);
        virtual bool OnPoke(const wxString& topic, const wxString& item, wxChar *data, int size, wxIPCFormat format);
        virtual bool OnStartAdvise(const wxString& topic, const wxString& item);
        virtual bool OnStopAdvise(const wxString& topic, const wxString& item);
        virtual bool Advise(const wxString& item, wxCStrData data, int size = -1, wxIPCFormat format = wxIPC_TEXT);
        virtual bool OnDisconnect();
protected:
        void Log(const wxString& command, const wxString& topic, const wxString& item, wxChar *data, size_t size, wxIPCFormat format);
public:
        wxString m_sAdvise, topic;
protected:
};

map<wxString, MyConnection *> connections;
class MyServer: public wxServer
{
public:
        MyServer();
        ~MyServer();
        void Disconnect();
        bool IsConnected() //{ return m_connection != NULL; };
        {
                return !connections.empty();
        }
        MyConnection *GetConnection(const wxString &topic) //{ return m_connection; };
        {
                map<wxString, MyConnection *>::iterator i = connections.find(topic);
                MyConnection *conn = 0;
                if (i != connections.end())
                        conn = i->second;
                return conn;
        }
        void Advise();
        bool CanAdvise(MyConnection *conn) { return conn != NULL && !conn->m_sAdvise.IsEmpty(); };
        bool CanAdvise() //{ return m_connection != NULL && !m_connection->m_sAdvise.IsEmpty(); };
        {
                map<wxString, MyConnection *>::iterator i = connections.begin();
                MyConnection *conn = 0;
                for(; i != connections.end(); i++) {
                        conn = i->second;
                        if (conn && !conn->m_sAdvise.IsEmpty())
                                break;
                }
                return conn != 0 && !conn->m_sAdvise.IsEmpty();
        }
        wxConnectionBase *OnAcceptConnection(const wxString& topic);

protected:
};
// ----------------------------------------------------------------------------
// MyServer
// ----------------------------------------------------------------------------
MyServer::MyServer() : wxServer()
{
}

MyServer::~MyServer()
{
        Disconnect();
}

wxConnectionBase *MyServer::OnAcceptConnection(const wxString& topic)
{
        //wxLogMessage(_T("OnAcceptConnection(\"%s\")"), topic.c_str());
	if (connections.find(topic) == connections.end()) {
	        MyConnection *conn = new MyConnection(topic);
		connections.insert(map<wxString, MyConnection *>::value_type(topic, conn));
	        //wxLogMessage(_T("Connection accepted"));
	        return conn;
	}
	return 0;
}

void MyServer::Disconnect()
{
	map<wxString, MyConnection *>::iterator i = connections.begin();
	for(; i != connections.end(); i++) {
                wxString topic = i->first;
		MyConnection *conn = i->second;
		conn->Disconnect();
		delete conn;
                if (ipcOnDisconnected)
                        ipcOnDisconnected(topic);
	}
	connections.clear();
        wxLogMessage(_T("Disconnected client"));
}

void MyServer::Advise()
{
	map<wxString, MyConnection *>::iterator i = connections.begin();
	for(; i != connections.end(); i++) {
		MyConnection *conn = i->second;
		if (CanAdvise(conn)) {
		        wxString s = wxDateTime::Now().Format();
		        conn->Advise(conn->m_sAdvise, s.c_str());
		        s = wxDateTime::Now().FormatTime() + _T(" ") + wxDateTime::Now().FormatDate();
		        conn->Advise(conn->m_sAdvise, s.c_str(), (s.Length() + 1) * sizeof(wxChar));

		#if wxUSE_DDE_FOR_IPC
		        //wxLogMessage(_T("DDE Advise type argument cannot be wxIPC_PRIVATE. The client will receive it as wxIPC_TEXT, and receive the correct no of bytes, but not print a correct log entry."));
		#endif
		        char bytes[3];
		        bytes[0] = '1'; bytes[1] = '2'; bytes[2] = '3';
		        conn->Advise(conn->m_sAdvise, (wxChar *)bytes, 3, wxIPC_PRIVATE);
		}
	}
}

// ----------------------------------------------------------------------------
// MyConnection
// ----------------------------------------------------------------------------
MyConnection::MyConnection(const wxString &topic_)
            : wxConnection()
{
        topic = topic_;
}

MyConnection::~MyConnection()
{
}

bool MyConnection::OnExecute(const wxString& topic,
    wxChar *data, int size, wxIPCFormat format)
{
        if (ipcOnExecute)
                return ipcOnExecute(topic.c_str(), (const char *)data, size);
        return false;
}

bool MyConnection::OnPoke(const wxString& topic,
    const wxString& item, wxChar *data, int size, wxIPCFormat format)
{
        if (ipcOnPoke)
                return ipcOnPoke(topic.c_str(), 
			(char *)(const char *)item.c_str(), 
			(char *)(const char *)data, size);
        return wxConnection::OnPoke(topic, item, data, size, format);
}

const void*MyConnection::OnRequest(const wxString& topic,
    const wxString& item, int *size, wxIPCFormat format)
{
        const char*data = 0;
        if (ipcOnRequest)
                data = ipcOnRequest(topic.c_str(), item.c_str(), size);
        return data;
}

bool MyConnection::OnStartAdvise(const wxString& topic,
                                 const wxString& item)
{
        m_sAdvise = item;
        if (ipcOnStartAdvise)
                return ipcOnStartAdvise(topic.c_str(), item.c_str());
        return true;
}

bool MyConnection::OnStopAdvise(const wxString& topic,
                                 const wxString& item)
{
        m_sAdvise.Empty();
        if (ipcOnStopAdvise)
                return ipcOnStopAdvise(topic.c_str(), item.c_str());
        return true;
}

void MyConnection::Log(const wxString& command, const wxString& topic,
    const wxString& item, wxChar *data, size_t size, wxIPCFormat format)
{
        wxString s;
        if (topic.IsEmpty() && item.IsEmpty())
                s.Printf(_T("%s("), command.c_str());
        else if (topic.IsEmpty())
                s.Printf(_T("%s(\"%s\","), command.c_str(), item.c_str());
        else if (item.IsEmpty())
                s.Printf(_T("%s(\"%s\","), command.c_str(), topic.c_str());
        else
                s.Printf(_T("%s(\"%s\",\"%s\","), command.c_str(), topic.c_str(), item.c_str());

        if (format == wxIPC_TEXT || format == wxIPC_UNICODETEXT)
                wxLogMessage(_T("%s\"%s\",%d)"), s.c_str(), data, size);
        else if (format == wxIPC_PRIVATE)
        {
                if (size == 3)
                {
                        char *bytes = (char *)data;
                        wxLogMessage(_T("%s'%c%c%c',%d)"), s.c_str(), bytes[0], bytes[1], bytes[2], size);
                }
                else
                        wxLogMessage(_T("%s...,%d)"), s.c_str(), size);
        }
        else if (format == wxIPC_INVALID)
                wxLogMessage(_T("%s[invalid data],%d)"), s.c_str(), size);
}

bool MyConnection::Advise(const wxString& item, wxCStrData data, int size, wxIPCFormat format)
{
        return wxConnection::Advise(item, data, size, format);
}

bool MyConnection::OnDisconnect()
{
        wxLogMessage(_T("%s OnDisconnect()"), topic.c_str());
	map<wxString, MyConnection *>::iterator i = connections.find(topic);
	bool ret = i != connections.end();
	if (ret) {
		MyConnection *conn = i->second;
		assert(conn == this);
		conn->Disconnect();
		connections.erase(i);
		if (ipcOnDisconnected)
                        ipcOnDisconnected(topic);
		delete conn;
	}
        return ret;
}

MyServer *server;
};//namespace IPCSERVER {
using namespace IPCSERVER;
// a sample exported function
bool SomeFunction(const char *sometext)
{
    MessageBoxA(0, sometext, "DLL Message", MB_OK | MB_ICONINFORMATION);
    return true;
}
bool ipc_active(const char *service_)
{
        server = new MyServer;
        wxString service = service_;
        if (!server->Create(service)) {
                delete server;
                server = NULL;
        }
        return server != 0;
}
void ipc_stop()
{
        delete server;
        server = 0;
}
bool ipc_advise(const char *topic, char *data, size_t size)
{
        size_t max = wxBase64EncodedSize(size);
        vector<char> output;
        output.resize(max);
        size_t output_len = 0;
        char *buf = 0;
        if (max > 0) {
                buf = &output[0];
		output_len = wxBase64Encode(buf, max, data, size);
                output[output_len] = 0;
        }

	map<wxString, MyConnection *>::iterator i = connections.find(topic);
	bool ret = i != connections.end();
	if (ret) {
		MyConnection *conn = i->second;
		ret = server->CanAdvise(conn);
		if (ret) {
		        conn->Advise(conn->m_sAdvise, buf, output_len);
                }
	}
	return ret;
}
void ipc_set_on_execute(bool (*OnExecute)(const char *topic, const char *data, int size))
{
        ipcOnExecute = OnExecute;
}
void ipc_set_on_poke(bool (*OnPoke)(const char *topic, const char *item, char *data, int size))
{
        ipcOnPoke = OnPoke;
}
void ipc_set_on_request(const char *(*OnRequest)(const char *topic, const char *item, int *size))
{
        ipcOnRequest = OnRequest;
}
void ipc_set_on_startAdvise(bool (*OnStartAdvise)(const char *topic, const char *item))
{
        ipcOnStartAdvise = OnStartAdvise;
}
void ipc_set_on_stopAdvise(bool (*OnStopAdvise)(const char *topic, const char *item))
{
        ipcOnStopAdvise = OnStopAdvise;
}
void ipc_set_on_disconnected(void (*OnDisconnected)(const char *topic))
{
        ipcOnDisconnected = OnDisconnected;
}
namespace IPCCLIENT {
bool (*ipcOnAdvise)(const char *topic, const char *item, char *data, size_t size);
void (*ipcOnDisconnect)();
class MyConnection: public wxConnection
{
public:
    bool Execute(const char*data, size_t size = -1, wxIPCFormat format = wxIPC_TEXT);
    const void *Request(const wxString& item, size_t *size = NULL, wxIPCFormat format = wxIPC_TEXT);
    bool Poke(const wxString& item, const char*data, size_t size = -1, wxIPCFormat format = wxIPC_PRIVATE);
    virtual bool OnAdvise(const wxString& topic, const wxString& item, const char*data, size_t size, wxIPCFormat format);
    virtual bool OnDisconnect();
protected:
    void Log(const wxString& command, const wxString& topic,
        const wxString& item, wxChar *data, size_t size, wxIPCFormat format);
};
// ----------------------------------------------------------------------------
// MyConnection
// ----------------------------------------------------------------------------

void MyConnection::Log(const wxString& command, const wxString& topic,
    const wxString& item, wxChar *data, size_t size, wxIPCFormat format)
{
    wxString s;
    if (topic.IsEmpty() && item.IsEmpty())
        s.Printf(_T("%s("), command.c_str());
    else if (topic.IsEmpty())
        s.Printf(_T("%s(item=\"%s\","), command.c_str(), item.c_str());
    else if (item.IsEmpty())
        s.Printf(_T("%s(topic=\"%s\","), command.c_str(), topic.c_str());
    else
        s.Printf(_T("%s(topic=\"%s\",item=\"%s\","), command.c_str(), topic.c_str(), item.c_str());

    if (format == wxIPC_TEXT || format == wxIPC_UNICODETEXT)
        wxLogMessage(_T("%s\"%s\",%d)"), s.c_str(), data, size);
    else if (format == wxIPC_PRIVATE)
    {
        if (size == 3)
        {
            char *bytes = (char *)data;
            wxLogMessage(_T("%s'%c%c%c',%d)"), s.c_str(), bytes[0], bytes[1], bytes[2], size);
        }
        else
            wxLogMessage(_T("%s...,%d)"), s.c_str(), size);
    }
    else if (format == wxIPC_INVALID)
        wxLogMessage(_T("%s[invalid data],%d)"), s.c_str(), size);
}

bool MyConnection::OnAdvise(const wxString& topic, const wxString& item, const char*data,
    size_t size, wxIPCFormat format)
{
        size_t max = wxBase64DecodedSize(size);
        vector<char> output;
        output.resize(max);
        size_t output_len = 0;
        char *buf = 0;
        if (max > 0) {
                buf = &output[0];
		output_len = wxBase64Decode(buf, max, (const char *)data, size);
        }
        Log(_T("OnAdvise"), topic, item, (wxChar *)data, output_len, format);
        bool ret = ipcOnAdvise;
        if (ret) {
                ret = ipcOnAdvise(topic.c_str(), item.c_str(), buf, output_len);
        }
        return ret;
}

bool MyConnection::OnDisconnect()
{
    wxLogMessage(_T("OnDisconnect()"));
    if (ipcOnDisconnect)
        ipcOnDisconnect();
    return true;
}

bool MyConnection::Execute(const char*data, size_t size, wxIPCFormat format)
{
    Log(_T("Execute"), wxEmptyString, wxEmptyString, (wxChar *)data, size, format);
    bool retval = wxConnection::Execute(data, size, format);
    return retval;
}

const void *MyConnection::Request(const wxString& item, size_t *size, wxIPCFormat format)
{
    const void *data = wxConnection::Request(item, size, format);
    Log(_T("Request"), wxEmptyString, item, (wxChar *)data, size ? *size : -1, format);
    return data;
}

bool MyConnection::Poke(const wxString& item, const char*data, size_t size, wxIPCFormat format)
{
	Log(_T("Poke"), wxEmptyString, item, (wxChar *)data, size, format);
	return wxConnection::Poke(item, (const void *)data, size, format);
}
class MyClient: public wxClient
{
public:
    MyClient();
    ~MyClient();
    bool Connect(const wxString& sHost, const wxString& sService, const wxString& sTopic);
    void Disconnect();
    wxConnectionBase *OnMakeConnection();
    bool IsConnected() { return m_connection != NULL; };
    MyConnection *GetConnection() { return m_connection; };

protected:
    MyConnection     *m_connection;
};
// ----------------------------------------------------------------------------
// MyClient
// ----------------------------------------------------------------------------
MyClient::MyClient() : wxClient()
{
    m_connection = NULL;
}

bool MyClient::Connect(const wxString& sHost, const wxString& sService, const wxString& sTopic)
{
	// suppress the log messages from MakeConnection()
	wxLogNull nolog;
	m_connection = (MyConnection *)MakeConnection(sHost, sService, sTopic);
	return m_connection != 0;
}
wxConnectionBase *MyClient::OnMakeConnection()
{
    return new MyConnection;
}

void MyClient::Disconnect()
{
    if (m_connection)
    {
        m_connection->Disconnect();
        delete m_connection;
        m_connection = NULL;
        wxLogMessage(_T("Client disconnected from server"));
    }
}
MyClient::~MyClient()
{
    Disconnect();
}
MyClient *client;
};//namespace IPCCLIENT {
using namespace IPCCLIENT;
bool ipc_connect(const char *hostname, const char *servername, const char *topic)
{
        client = new MyClient;
        bool ret = client->Connect(hostname, servername, topic);
        if (!ret) {
                delete client;
                client = 0;
        }
        return ret;
}
void ipc_disconnect()
{
        if (client) {
                client->Disconnect();
        }
        delete client;
        client = 0;
}
void ipc_set_on_disconnect(void (*OnDisconnect)())
{
        ipcOnDisconnect = OnDisconnect;
}
void ipc_startAdvise(const char *something)
{
        if (client) {
                client->GetConnection()->StartAdvise(wxString(something));
        }
}
void ipc_stopAdvise(const char *something)
{
        if (client) {
                client->GetConnection()->StopAdvise(wxString(something));
        }
}
void ipc_set_on_advise(bool (*OnAdvise)(const char *topic, const char *item, char *data, size_t size))
{
        ipcOnAdvise = OnAdvise;
}
void ipc_execute(const char *data, size_t size)
{
        if (client) {
                client->GetConnection()->Execute(data, size);
        }
}
bool ipc_poke(const char *item, const char *data, size_t size)
{
        bool ret = client != 0;
        if (ret) {
		IPCCLIENT::MyConnection *conn = client->GetConnection();
		ret = conn != 0;
		if (ret)
	                ret = conn->Poke(item, data, size);
        }
        return ret;
}
int ipc_request(const char *item, char *buf)
{
        size_t size = 0;
        if (client) {
                const void *data = client->GetConnection()->Request(wxString(item), &size);
        }
        return size;
}


